/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Kernel Module                                  */
/*                                                                           */
/*    Copyright (C) 2008 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    W25X16, W25X32, W25X64 FLASH driver module                             */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Includes
#include <krnlSystemTimer.h>
#include <drvEEPROM.h>
#include <drvSPI.h>
#include "drvIOConfig.h"

///////////////////////////////////////////////////////////////////////////////
// Constants

// EEPROM commands
#define SEE_WRSR					1			// write status register
#define SEE_WRITE					2			// write command
#define SEE_READ					3			// read command
#define SEE_WDI						4			// write disable
#define SEE_STAT					5			// read status register
#define SEE_WEN						6			// write enable

///////////////////////////////////////////////////////////////////////////////
// Module global variables


///////////////////////////////////////////////////////////////////////////////
// Initialize EEPROM
void drvInitEEPROM( void)
{
	InitEEPROMSPI();
	InitEEPROMCS();
}

///////////////////////////////////////////////////////////////////////////////
// Read status
dosByte drvEEPROMReadStatus( void)
{
	// Check the Serial EEPROM status register
	dosByte status;
	
	EEPROMCS(PIN_LOW);									// select the Serial EEPROM
	EEPROMSendReceiveByte( SEE_STAT );	// send a READ STATUS COMMAND
	status = EEPROMSendReceiveByte(0);	// send/receive
	EEPROMCS(PIN_HIGH);									// deselect terminate command
	
	return status;
}

///////////////////////////////////////////////////////////////////////////////
// Wait for EEPROM busy (flase if timeout occured)
dosBool drvEEPROMWaitForBusy(dosWord in_max_wait_in_ms)
{
	dosWord start_time = krnlGetSystemTimer();
	dosWord diff_time;

	// wait (with timeout) if display is busy
	do
	{
		diff_time = krnlGetSystemTimer() - start_time;
	}	while( (drvEEPROMReadStatus() & 0x1) && diff_time < in_max_wait_in_ms);
	
  return diff_time < in_max_wait_in_ms;
}	


///////////////////////////////////////////////////////////////////////////////
// Write enable
void drvEEPROMWriteEnable(void)
{
	// send a Write Enable command
	EEPROMCS(PIN_LOW);									// select the Serial EEPROM
	EEPROMSendReceiveByte(SEE_WEN);			// write enable command
	EEPROMCS(PIN_HIGH);									// deselect terminate command
}

///////////////////////////////////////////////////////////////////////////////
// read block from eeprom
void drvEEPROMReadBlock( dosWord in_address, dosByte* out_buffer, dosWord in_length)
{
	dosWord i;
	
	// select eeprom and send read command and address
	EEPROMCS(PIN_LOW);									// select the Serial EEPROM
	EEPROMSendReceiveByte(SEE_READ);		// read command
	
	EEPROMSendReceiveByte( LOW(in_address) ); // address LSB (word aligned)
	
	// read block data
	for( i = 0; i < in_length; i++ )
	{
		out_buffer[i] = EEPROMSendReceiveByte(0); // send dummy, read byte
	}
	
	// relase CS
	EEPROMCS(PIN_HIGH);									// deselect terminate command
}

///////////////////////////////////////////////////////////////////////////////
// write block to eeprom
dosBool drvEEPROMWriteBlock( dosWord in_address, dosByte* in_buffer, dosWord in_length)
{
	dosWord i;
	dosBool success = dosTrue;
	
	// Set the Write Enable Latch
	drvEEPROMWriteEnable();
	
	// perform the write sequence
	EEPROMCS(PIN_LOW);												// select the Serial EEPROM
	EEPROMSendReceiveByte(SEE_WRITE);					// write command
	EEPROMSendReceiveByte(LOW(in_address));		// address LSB (word aligned)

	// read block data
	for( i = 0; i < in_length && success; i++ )
	{
		EEPROMSendReceiveByte(in_buffer[i]); // read dummy, write byte
		
		// check for page boundary
		if( (i & 0x0f) == 0x0f)
		{
			EEPROMCS(PIN_HIGH);								// deselect to start write operation
			
			success = drvEEPROMWaitForBusy(10);					// wait for write to complete
			
			if( success)
			{
				// Set the Write Enable Latch
				drvEEPROMWriteEnable();
				
				// perform the write sequence
				EEPROMCS(PIN_LOW);												// select the Serial EEPROM
				EEPROMSendReceiveByte(SEE_WRITE);					// write command
				EEPROMSendReceiveByte(LOW(in_address+i+1));	// address LSB (word aligned)
			}
		}
	}

	// relase CS
	EEPROMCS(PIN_HIGH);										// deselect terminate command

	// wait for busy	
	if( success )
		success = drvEEPROMWaitForBusy( 20 );
	
	return success;
}
